/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.core.awt;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.*;
import javax.swing.JComponent;
import javax.swing.UIManager;
/** A class that produces a tab control component. It can be used
* separately or in a PageControl.
*
* <P>
* <TABLE BORDER COLS=3 WIDTH=100%>
* <TR><TH WIDTH=15%>Property<TH WIDTH=15%>Property Type<TH>Description
* <TR><TD> Direction <TD> boolean <TD> Direction (upper or lower Tabcontrol)
* <TR><TD> SelectedIndex <TD> int <TD> Index of selected tab.
* </TABLE>
*
* TO DO:
* 1) Text is painted at wrong position.
*
* @version 2.21, May 22, 1998
* @author Petr Hamernik, Ian Formanek
*/
public class TabControl extends JComponent implements Serializable {
/** generated Serialized Version UID */
static final long serialVersionUID = 7374902493096688301L;
/** The style of TabControl where the tabs are along the upper edge. */
public static final boolean DIR_UP = true;
/** The style of TabControl where the tabs are along the upper edge. */
public static final boolean DIR_DOWN = false;
/** The default style of TabControl. */
private static final boolean DEFAULT_DIR = DIR_UP;
/** Current style of TabControl. One of DIR_UP / DIR_DOWN. */
protected boolean direction;
/** Guarantee minimum width of each tab.
* @see #setMinWidth
* @see #getMinWidth
*/
protected int minWidth;
/** Default value of guarantee minimum width of each tab. */
private static final int DEFAULT_MIN_WIDTH = 50;
/** Currently selected tab.
* @see #setSelectedIndex
* @see #getSelectedIndex
*/
private int selectedIndex;
/** Vector of listeners which are invoked when the selction has changed.
* @associates PropertyChangeListener*/
transient private Vector indexListeners = new Vector();
/** Vector of Strings with names of all tabs.
* @associates String*/
protected Vector tabs;
/** Indent of labels each tab. */
protected static final int indent = 4;
/** Width of lines among the tabs in pixels. */
protected static final int margin = 1;
/** Vector of sizes each tab. Depends on font size and the width
* of the label.
* @associates Integer
*/
protected Vector sizes;
/** First visible tab in case that not all tabs are together
* visible. When the width of TabControl component is less than
* needed minimum width at the right side will appear small
* spin buttons for shifting visible tabs.
* @see #lastVisible
* @see #allVisible
*/
protected int firstVisible;
/** Last visible tab in case that not all tabs are together
* visible. When the width of TabControl component is less than
* needed minimum width at the right side will appear small
* spin buttons for shifting visible tabs.
* @see #firstVisible
* @see #allVisible
*/
protected int lastVisible;
/** Flag if all tabs are visible.
* @see #firstVisible
* @see #lastVisible
*/
protected boolean allVisible;
/** Size of small spin buttons for shifting of the visible
* tabs.
* @see #allVisible
*/
protected static final int posButtonSize = 10;
/** Create new TabControl with all default values. */
public TabControl() {
tabs = new Vector();
sizes = new Vector();
minWidth = DEFAULT_MIN_WIDTH;
direction = DIR_UP;
Color defaultBackground = UIManager.getColor("TabbedPane.selected"); // NOI18N
Color defaultForeground = UIManager.getColor("TabbedPane.tabForeground"); // NOI18N
Font defaultFont = UIManager.getFont("TabbedPane.font"); // NOI18N
/* "TabbedPane.font", dialogPlain12,
"TabbedPane.tabBackground", table.get("controlShadow"),
"TabbedPane.tabForeground", black,
"TabbedPane.tabHighlight", table.get("controlHighlight"),
"TabbedPane.tabShadow", table.get("controlShadow"),
"TabbedPane.tabDarkShadow", table.get("controlDkShadow"),
"TabbedPane.focus", getFocusColor(),
"TabbedPane.selected", table.get("control"), */ // NOI18N
setBackground(defaultBackground);
setForeground(defaultForeground);
setFont(defaultFont);
}
/** Remove all tabs and sets all variables into consistent state. */
synchronized private void clearTabs() {
tabs.removeAllElements();
sizes.removeAllElements();
firstVisible = 0;
lastVisible = 0;
allVisible = true;
int old = selectedIndex;
selectedIndex = -1;
if (selectedIndex != old)
fireIndexChange(old, selectedIndex);
}
/** Select new tab.
* @param index Index of new selected tab.
* @exception ArrayIndexOutOfBoundsException If the value was invalid.
* @see #selectedIndex
* @see #getSelectedIndex
*/
synchronized public void setSelectedIndex(int index) throws ArrayIndexOutOfBoundsException {
if ((index < 0) || (index >= tabs.size()))
throw new ArrayIndexOutOfBoundsException();
int old = selectedIndex;
selectedIndex = index;
fireIndexChange(old, selectedIndex);
repaint();
}
/** Gets the index of currently selected tab.
* @return Index of selected tab.
* @see #selectedIndex
* @see #setSelectedIndex
*/
public int getSelectedIndex() {
return selectedIndex;
}
/** Returns the number of tabs.
* @return number of tabs.
*/
public int getTabCount() {
return tabs.size();
}
/** Converts point to index of a tab.
* @param x x-coordinate on the component
* @return index of the tab shown under the point
*/
public int pointToIndex (int x) {
int sum = 0;
for (int i = firstVisible; i <= lastVisible; i++) {
sum += ((Integer) sizes.elementAt(i)).intValue();
if (x < sum) {
return i;
}
}
return lastVisible;
}
/** Gets the name of the tab with the specific index.
* @param index Index of tab.
* @exception ArrayIndexOutOfBoundsException If the value was invalid.
* @return Name of tab.
*/
public String getTabLabel(int index) throws ArrayIndexOutOfBoundsException {
return (String) tabs.elementAt(index);
}
/** Add new tab.
* @param newTab Name of new tab.
*/
synchronized public void addTab(String newTab) {
int index = tabs.size();
addTabAt(newTab, index);
}
/** Add new tab at specific position.
* @param newTab Name of new tab.
* @param index Position of new tab.
* @exception ArrayIndexOutOfBoundsException If the value was invalid.
*/
synchronized public void addTabAt(String newTab, int index) throws
ArrayIndexOutOfBoundsException {
int w = stringWidth(getFontM(), newTab) + 2 * (indent + margin) + 3;
if (w < minWidth)
w = minWidth;
tabs.insertElementAt(newTab, index);
sizes.insertElementAt(new Integer(w), index);
setSelectedIndex(0);
checkWidth(true);
repaint();
}
/** Removes the tab at the specific position.
* @param index Position of tab.
* @exception ArrayIndexOutOfBoundsException If the value was invalid.
*/
synchronized public void removeTabAt(int index) throws
ArrayIndexOutOfBoundsException {
tabs.removeElementAt(index);
sizes.removeElementAt(index);
if (tabs.size() == 0) {
clearTabs();
return;
}
if (selectedIndex > index) {
if (selectedIndex > 0)
selectedIndex--;
}
else {
if (selectedIndex == index) {
if (selectedIndex == tabs.size())
selectedIndex--;
fireIndexChange(index, selectedIndex);
}
}
if (firstVisible > 0) firstVisible--;
if (lastVisible > 0) lastVisible--;
checkWidth(true);
repaint();
}
/** Remove all tabs.
* @see #removeTabAt
*/
public void removeAllTabs() {
clearTabs();
repaint();
}
/** Sets the TabControl to be visible tab with the specific
* index.
* @see #firstVisible
* @see #lastVisible
* @see #allVisible
*/
synchronized public void makeVisible(int index) {
if ((index < firstVisible) || (index > lastVisible)) {
firstVisible = index;
lastVisible = index;
checkWidth(true);
repaint();
}
}
synchronized private void checkWidth(boolean dir) {
Dimension d = getSize();
int w = countTotalSize();
if (w <= d.width) {
allVisible = true;
firstVisible = 0;
lastVisible = tabs.size() - 1;
}
else {
int wMax = d.width - (4 * margin + 2 * posButtonSize + 2 * indent);
if (wMax < 0) {
firstVisible = 0;
lastVisible = 0;
allVisible = false;
}
else {
if (allVisible) {
allVisible = false;
if (countTotalSize(0, selectedIndex) < wMax) {
firstVisible = 0;
for (lastVisible = selectedIndex; lastVisible < sizes.size(); lastVisible++)
if (countTotalSize(0, lastVisible) > wMax) {
lastVisible--;
break;
}
}
else {
lastVisible = selectedIndex;
for (firstVisible = 0; firstVisible <= lastVisible; firstVisible++)
if (countTotalSize(firstVisible, lastVisible) < wMax)
break;
}
}
else {
if (countTotalSize(firstVisible, lastVisible) < wMax) {
for (firstVisible = 0; firstVisible < lastVisible; firstVisible++)
if (countTotalSize(firstVisible, lastVisible) < wMax) {
break;
}
for (; lastVisible < sizes.size() - 1; lastVisible++)
if (countTotalSize(firstVisible, lastVisible) > wMax) {
lastVisible--;
break;
}
}
else { // dir = true (reduce from left), dir = false (reduce from right)
if (dir) {
for (; firstVisible < lastVisible; firstVisible++)
if (countTotalSize(firstVisible, lastVisible) < wMax)
break;
}
else {
for (; lastVisible > firstVisible; lastVisible--)
if (countTotalSize(firstVisible, lastVisible) < wMax)
break;
}
}
}
}
}
}
/** Sets a new direction of TabControl. If it is true,
* TabControl direction down otherwise up.
* @param direct New direction.
* @see #direction
* @see #getDirection
*/
public void setDirection(boolean direct) {
if (direction != direct) {
direction = direct;
repaint();
}
}
/** Gets the current direction of TabControl. If it is true,
* TabControl direction down otherwise up.
* @return Current direction.
* @see #direction
* @see #setDirection
*/
public boolean getDirection() {
return direction;
}
/** Sets the minimum width of each tab.
* @aMinWidth New value of minWidth variable.
* @see #minWidth
* @see #getMinWidth
*/
public void setMinWidth(int aMinWidth) {
minWidth = aMinWidth;
countSizes();
checkWidth(true);
repaint();
}
/** Gets the minimum width of each tab.
* @return Current value of minWidth variable.
* @see #minWidth
* @see #setMinWidth
*/
public int getMinWidth() {
return(minWidth);
}
public synchronized void setFont(Font f) {
super.setFont(f);
countSizes();
checkWidth(true);
repaint();
}
public void paint(Graphics g) {
Dimension s = getSize();
if (tabs.size() > 0) {
int horizTrans = 2;
int start = 0;
FontMetrics f = getFontM();
int fontYPos;
if (s.height < f.getHeight())
fontYPos = s.height;
else
fontYPos = (s.height + f.getHeight()) / 2 - 1;
int focusStart = 0;
int focusWidth = 0;
if (allVisible) {
firstVisible = 0;
lastVisible = sizes.size() - 1;
}
for (int t = firstVisible; t <= lastVisible; t++) {
int top = 0;
int left = start;
int height = s.height - 1;
int width = ((Integer) sizes.elementAt(t)).intValue();
int fontYPosDelta = 0;
if (selectedIndex == t) {
focusStart = start;
focusWidth = width - 1;
if (!direction) {
fontYPosDelta -= horizTrans;
top -= horizTrans;
}
else {
height += margin;
}
}
else {
if (direction) {
top += horizTrans;
fontYPosDelta += horizTrans;
}
}
g.setColor(getForeground());
String str = new String((String) tabs.elementAt(t));
int delta = f.stringWidth(str);
delta = ((width - delta - 2* (margin + indent)) / 2);
g.drawString(str, start + margin + indent + delta, fontYPos + fontYPosDelta);
start += width;
width -=1;
g.setColor(Color.black);
g.drawLine(left + width, top, left + width, top + height);
g.setColor(SystemColor.controlHighlight);
width -=1;
if ((selectedIndex == t) && (!direction)) {
top -= margin;
height += margin;
}
for(int i = 0; i < margin; i++) {
g.draw3DRect(left, top, width, height, true);
left++;
top++;
width = width - 2;
height = height - 2;
}
}
if (!allVisible) {
int top = s.height - indent - 2 * margin - posButtonSize;
int height = posButtonSize + 2 * margin;
int width = height;
int left = s.width - indent - 4 * margin - 2 * posButtonSize;
int[] xP = new int[3];
int[] yP = new int[3];
int os = left + 2 * margin + posButtonSize;
int difr = posButtonSize / 10;
for (int q = 0; q <= 1; q++) {
boolean b = true;
switch (q) {
case 0: xP[0] = os - margin - posButtonSize + difr;
xP[1] = os - margin - difr;
xP[2] = xP[1];
yP[0] = top + margin + posButtonSize / 2;
yP[1] = top + margin + difr;
yP[2] = top + margin + posButtonSize - difr;
if (firstVisible == 0)
b = false;
break;
case 1: for (int j = 0; j <= 2; j++)
xP[j] = 2 * os - xP[j];
if (lastVisible == tabs.size() - 1)
b = false;
}
if (b) {
g.setColor(SystemColor.controlHighlight);
for (int i = 0; i < margin; i++)
g.draw3DRect(left + i, top + i, width - 2*i, height - 2*i, true);
g.setColor(getForeground());
g.fillPolygon(xP, yP, 3);
}
left += 2 * margin + posButtonSize;
}
}
if (direction) {
g.setColor(SystemColor.controlHighlight);
for (int i = 0; i < margin; i++) {
g.draw3DRect(0, s.height - i - 1, focusStart + margin, margin, true);
g.draw3DRect(focusStart + focusWidth - margin + i + 1, s.height - i - 1,
s.width - focusStart - focusWidth + margin, margin, true);
}
g.setColor(getBackground());
g.fillRect(focusStart + margin, s.height - margin - 1, 1, margin + 1);
}
else {
g.setColor(SystemColor.controlHighlight);
for (int i = 0; i < margin; i++) {
if (selectedIndex != firstVisible)
g.draw3DRect(margin - i - 1, -1, focusStart, 1 + i, true);
g.draw3DRect(focusStart + focusWidth - margin - 1, -1,
s.width - focusStart - focusWidth + margin, 1 + i, true);
}
g.setColor(getBackground());
g.fillRect(focusStart + focusWidth - margin - 1 , 0, 1, margin);
g.setColor(Color.black);
g.drawLine(focusStart, s.height - horizTrans, focusStart + focusWidth, s.height - horizTrans);
g.drawLine(focusStart + focusWidth, margin, s.width, margin);
if (selectedIndex != firstVisible)
g.drawLine(0, margin, focusStart - 1, margin);
}
}
else {
g.setColor(getBackground());
g.fillRect(0, 0, s.width, s.height);
g.setColor(getForeground());
g.drawRect(0, 0, s.width - 1, s.height - 1);
}
}
public Dimension getMinimumSize() {
int w = countMaxSize();
FontMetrics f = getFontM();
if (sizes.size() > 1) {
w += 2 * indent + 2 * posButtonSize + 4 * margin;
w = Math.min(w, countTotalSize());
}
int h = 2 * margin + f.getHeight() * 4 / 3;
return new Dimension(w, h);
}
public Dimension getPreferredSize() {
int w = countTotalSize();
if (w == 0)
w = 2 * margin;
Font font = getFont();
FontMetrics f = getFontMetrics(
font==null ? new Font("Helvetica", Font.PLAIN, 12) : font); // NOI18N
int h = 2 * margin + f.getHeight() * 4 / 3;
return new Dimension(w, h);
}
public void setSize(int width, int height) {
super.setSize(width, height);
int a = firstVisible;
int b = lastVisible;
checkWidth(false);
if ((a != firstVisible) || (b != lastVisible))
repaint();
}
public void setSize(Dimension d) {
setSize(d.width, d.height);
}
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
int a = firstVisible;
int b = lastVisible;
checkWidth(false);
if ((a != firstVisible) || (b != lastVisible))
repaint();
}
private FontMetrics getFontM() {
Font font = getFont();
return getFontMetrics(
font==null ? new Font("Helvetica", Font.PLAIN, 12) : font); // NOI18N
}
private int stringWidth(FontMetrics f, String s) {
return f.stringWidth(s);
}
private void countSizes() {
int n = tabs.size();
sizes.removeAllElements();
if (n > 0) {
FontMetrics f = getFontM();
for (int i = 0; i < n; i++) {
int w = stringWidth(getFontM(), (String) tabs.elementAt(i)) + 2 * (indent + 1) + 1;
if (w < minWidth)
w = minWidth;
sizes.addElement(new Integer(w));
}
}
}
private int countTotalSize(int from, int till) {
int result = 0;
for (int i = from; i <= till; i++)
result += ((Integer) sizes.elementAt(i)).intValue();
return result;
}
private int countTotalSize() {
return countTotalSize(0, sizes.size() - 1);
}
private int countMaxSize() {
int result = 0;
for (int i = 0; i < sizes.size(); i++)
result = Math.max(result, ((Integer) sizes.elementAt(i)).intValue());
return result;
}
protected void processMouseEvent (MouseEvent e) {
super.processMouseEvent (e);
if (e.isConsumed () || e.getID () != MouseEvent.MOUSE_PRESSED) {
return;
}
int x = e.getX();
int y = e.getY();
int sum = 0;
int i;
int old = selectedIndex;
if (old == -1)
return;
for (i = firstVisible; i <= lastVisible; i++) {
sum += ((Integer) sizes.elementAt(i)).intValue();
if (x < sum)
break;
}
if ((x < sum) && (selectedIndex != i))
setSelectedIndex(i);
else {
if (!allVisible) {
Dimension s = getSize();
int tmp = s.height - indent;
if ((y >= tmp - 2 * margin - posButtonSize) && (y < tmp)) {
tmp = s.width - indent - 4 * margin - 2 * posButtonSize;
if ((x >= tmp) && (x < tmp + 2 * margin + posButtonSize) &&
(firstVisible > 0)) {
firstVisible--;
checkWidth(false);
repaint();
}
if ((x >= tmp + 2 * margin + posButtonSize) &&
(x < tmp + 4 * margin + 2 * posButtonSize) && (lastVisible < tabs.size() - 1)) {
lastVisible++;
checkWidth(true);
repaint();
}
}
}
}
}
public void addIndexChangeListener(PropertyChangeListener l) {
if (indexListeners == null) indexListeners = new Vector ();
indexListeners.addElement(l);
}
public void removeIndexChangeListener(PropertyChangeListener l) {
if (indexListeners == null) indexListeners = new Vector ();
indexListeners.removeElement(l);
}
protected void fireIndexChange(int oldValue, int newValue) {
if (indexListeners == null) indexListeners = new Vector ();
Vector newListeners = (Vector)indexListeners.clone();
Enumeration en = newListeners.elements();
PropertyChangeEvent evt = new PropertyChangeEvent(this, "selectedIndex", // NOI18N
new Integer(oldValue), new Integer(newValue));
while (en.hasMoreElements())
((PropertyChangeListener) en.nextElement()).propertyChange(evt);
}
}
/*
* Log
* 6 src-jtulach1.5 1/12/00 Ales Novak i18n
* 5 src-jtulach1.4 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 4 src-jtulach1.3 9/10/99 Ian Formanek Removed deprecated code
* 3 src-jtulach1.2 8/16/99 Ian Formanek The TabControl height is
* smaller
* 2 src-jtulach1.1 7/28/99 Jaroslav Tulach Popup menu for
* workspaces.
* 1 src-jtulach1.0 3/9/99 Jaroslav Tulach
* $
* Beta Change History:
* 0 Tuborg 2.04 --/--/98 Jan Formanek added constants for direction (DIR_UP, DIR_DOWN), changed access rights
* 0 Tuborg 2.04 --/--/98 Jan Formanek of some class variables and initialization
* 0 Tuborg 2.06 --/--/98 Petr Hamernik small bug fixes.
* 0 Tuborg 2.07 --/--/98 Petr Hamernik small bug fixes.
* 0 Tuborg 2.10 --/--/98 Petr Hamernik changes in index listeners
* 0 Tuborg 2.20 --/--/98 Jan Formanek color is now taken from Swing's UIManager (same as TabbedPane)
* 0 Tuborg 2.21 --/--/98 Jan Formanek extends JComponewnt (==>> is lightweight, changes UI - colors)
*/